package com.mdroid.app;

import java.io.IOException;
import java.io.InputStream;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;

import android.app.ActivityManager;
import android.app.Application;
import android.app.ActivityManager.RunningTaskInfo;
import android.content.ComponentName;
import android.content.Context;
import android.os.Environment;

import com.mdroid.support.cache.ImageCache;
import com.mdroid.support.images.remote.RemoteImageLoader;

public abstract class MLibrary {
    private static MLibrary mInstance;
    
    // expire images after a day
    // TODO: this currently only affects the in-memory cache, so it's quite pointless
    private static final int DEFAULT_TTL_MINUTES = 24 * 60;

    /**
     * Used for receiving low memory system notification. You should definitely
     * use it in order to clear caches and not important data every time the
     * system needs memory.
     * 
     * @author Cyril Mottier
     * @see MLibrary#registerOnLowMemoryListener(OnLowMemoryListener)
     * @see MLibrary#unregisterOnLowMemoryListener(OnLowMemoryListener)
     */
    public static interface OnLowMemoryListener {
        
        /**
         * Callback to be invoked when the system needs memory.
         */
        public void onLowMemoryReceived();
    }

    private static final int CORE_POOL_SIZE = 5;

    private static final ThreadFactory sThreadFactory = new ThreadFactory() {
        private final AtomicInteger mCount = new AtomicInteger(1);

        public Thread newThread(Runnable r) {
            return new Thread(r, "GreenDroid thread #" + mCount.getAndIncrement());
        }
    };
    
    private Application mApplication;
    private ExecutorService mExecutorService;
    private ImageCache mImageCache;
    private RemoteImageLoader mImageLoader;
    private ArrayList<WeakReference<OnLowMemoryListener>> mLowMemoryListeners;
    
    public static MLibrary Instance() {
        return mInstance;
    }
    
    protected MLibrary(Application application) {
        if (mInstance != null) {
            throw new Error("Must be only one MLibrary instance.");
        }
        mLowMemoryListeners = new ArrayList<WeakReference<OnLowMemoryListener>>();
        mApplication = application;
        mInstance = this;
    }
    
    /**
     * Return an ExecutorService (global to the entire application) that may be
     * used by clients when running long tasks in the background.
     * 
     * @return An ExecutorService to used when processing long running tasks
     */
    public ExecutorService getExecutor() {
        if (mExecutorService == null) {
            mExecutorService = Executors.newFixedThreadPool(CORE_POOL_SIZE, sThreadFactory);
        }
        return mExecutorService;
    }

    /**
     * Return this application {@link ImageCache}.
     * 
     * @return The application {@link ImageCache}
     */
    public ImageCache getImageCache() {
        if (mImageCache == null) {
            mImageCache = newImageCache();
        }
        return mImageCache;
    }

    protected ImageCache newImageCache() {
        return new ImageCache(25, DEFAULT_TTL_MINUTES, CORE_POOL_SIZE);
    }
    
    public RemoteImageLoader getImageLoader() {
        if (mImageLoader == null) {
            mImageLoader = new RemoteImageLoader(mApplication);
        }
        return mImageLoader;
    }
    
    /**
     * Add a new listener to registered {@link OnLowMemoryListener}.
     * 
     * @param listener The listener to unregister
     * @see OnLowMemoryListener
     */
    public void registerOnLowMemoryListener(OnLowMemoryListener listener) {
        mLowMemoryListeners.add(new WeakReference<OnLowMemoryListener>(listener));
    }

    /**
     * Remove a previously registered listener
     * 
     * @param listener The listener to unregister
     * @see OnLowMemoryListener
     */
    public void unregisterOnLowMemoryListener(OnLowMemoryListener listener) {
        int i = 0;
        while (i < mLowMemoryListeners.size()) {
            final OnLowMemoryListener l = mLowMemoryListeners.get(i).get();
            if (l == null || l == listener) {
                mLowMemoryListeners.remove(i);
            } else {
                i++;
            }
        }
    }
    
    public void onLowMemery() {
        int i = 0;
        while (i < mLowMemoryListeners.size()) {
            final OnLowMemoryListener listener = mLowMemoryListeners.get(i).get();
            if (listener == null) {
                mLowMemoryListeners.remove(i);
            } else {
                listener.onLowMemoryReceived();
                i++;
            }
        }
    }
    
    public Application getApplication() {
        return mApplication;
    }
    
    public String getCachePath() {
        String rootDir = null;
        if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
            // SD-card available
            rootDir = Environment.getExternalStorageDirectory().getAbsolutePath() + "/Android/data/"
                    + mApplication.getPackageName() + "/cache";
        }
        return rootDir;
    }
    
    public String getUserAgent() {
        return "Android/mDroid";
    }
    
    public InputStream getAssetsStream(String fileName) throws IOException {
        return mApplication.getAssets().open(fileName);
    }

    /**
     * Checks if the application is in the background (i.e behind another application's Activity).
     * 
     * @return true if another application is above this one.
     */
    public boolean isApplicationBroughtToBackground() {
        ActivityManager am = (ActivityManager) mApplication.getSystemService(Context.ACTIVITY_SERVICE);
        List<RunningTaskInfo> tasks = am.getRunningTasks(1);
        if (!tasks.isEmpty()) {
            ComponentName topActivity = tasks.get(0).topActivity;
            if (!topActivity.getPackageName().equals(mApplication.getPackageName())) {
                return true;
            }
        }

        return false;
    }
}
